package com.lagou.web.framework.annotation.factory;


import com.lagou.web.framework.annotation.config.stereotype.*;
import com.lagou.web.framework.util.ClazzUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SingletonBeanFactory {
    private final List<ScanClass> scanClassList = new ArrayList<>();
    private final Map<String, Object> singletonMap = new HashMap<>();

    private void intContextConfig(String contextConfig) {
        readContextConfig(ClazzUtils.getClass(contextConfig));
    }

    private void readContextConfig(Class<?> configClass) {
        Annotation[] annotations = configClass.getAnnotations();
        if (annotations != null) {
            List<Class<?>> tempList = new ArrayList<>();
            for (Annotation annotation : annotations) {
                if (annotation.annotationType().equals(ComponentScan.class)) {
                    String[] packageNames = ((ComponentScan) annotation).value();
                    if (packageNames != null) {
                        for (String packageName : packageNames) {
                            tempList.addAll(ClazzUtils.loadClass(packageName));
                        }
                    }
                }
            }
            scanClassList.addAll(createScanClassList(tempList));
        }
    }

    private List<ScanClass> createScanClassList(List<Class<?>> list) {
        List<ScanClass> result = new ArrayList<>();
        for (Class<?> c : list) {
            ScanClass scanClass = new ScanClass();
            if (c.isInterface()) {
                scanClass.setInterfaceFlag(true);
                scanClass.setInterfaceClass(c);
            } else {
                scanClass.setInterfaceFlag(false);
                scanClass.setBeanClass(c);
            }
            result.add(scanClass);
        }
        int i = 0, j = 0;
        while (i < result.size()) {
            Class<?> interfaceClass = result.get(i).getInterfaceClass();
            if (interfaceClass == null) {
                ++i;
            } else {
                Class<?> beanClass = result.get(j).getBeanClass();
                if (beanClass != null && interfaceClass.isAssignableFrom(beanClass)) {
                    if (result.get(i).getInterfaceFlag()) {
                        result.get(i).setBeanClass(beanClass);
                    }
                    result.get(j).setInterfaceClass(interfaceClass);
                }
                ++j;
            }
            if (j == result.size()) {
                ++i;
                j = 0;
            }
        }
        return result;
    }

    private void setBeansToSingletonMap() {
        for (ScanClass scanClass : scanClassList) {
            Class<?> initClass = scanClass.getBeanClass();
            if (initClass != null && scanClass.getInterfaceFlag()) {
                initClass = scanClass.getInterfaceClass();
            }
            if (initClass != null) {
                Annotation[] annotations = initClass.getAnnotations();
                if (annotations != null) {
                    String beanName = null;
                    Class<?> beanClass = scanClass.getBeanClass();
                    for (Annotation annotation : annotations) {
                        if (annotation.annotationType().equals(Component.class)) {
                            beanName = ((Component) annotation).value();
                            if (beanName == null || beanName.length() == 0) {
                                beanName = (beanName = beanClass.getSimpleName()).substring(0, 1).toLowerCase() + beanName.substring(1);
                            }
                        } else if (annotation.annotationType().equals(Repository.class)) {
                            beanName = ((Repository) annotation).value();
                            if (beanName == null || beanName.length() == 0) {
                                beanName = (beanName = beanClass.getSimpleName()).substring(0, 1).toLowerCase() + beanName.substring(1);
                            }
                        } else if (annotation.annotationType().equals(Service.class)) {
                            beanName = ((Service) annotation).value();
                            if (beanName == null || beanName.length() == 0) {
                                beanName = (beanName = beanClass.getSimpleName()).substring(0, 1).toLowerCase() + beanName.substring(1);
                            }
                        } else if (annotation.annotationType().equals(Controller.class)) {
                            beanName = ((Controller) annotation).value();
                            if (beanName == null || beanName.length() == 0) {
                                beanName = (beanName = beanClass.getSimpleName()).substring(0, 1).toLowerCase() + beanName.substring(1);
                            }
                        }
                    }
                    if (beanName != null && !existInSingletonMap(beanName, beanClass)) {
                        singletonMap.put(beanName, ClazzUtils.getObject(beanClass));
                    }
                }
            }
        }
    }

    private boolean existInSingletonMap(String beanName, Class<?> beanClass) {
        if (singletonMap.get(beanName) != null) {
            return true;
        }
        for (Object bean : singletonMap.values()) {
            if (bean.getClass().getName().equals(beanClass.getName())) {
                return true;
            }
        }
        return false;
    }

    private void populateBeans() throws Exception {
        for (Object bean : singletonMap.values()) {
            Field[] fields = bean.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                Autowired annotation = field.getAnnotation(Autowired.class);
                if (annotation != null) {
                    Object bean4Autowired = getAutowiredBeanByField(field);
                    if (bean4Autowired != null) {
                        field.set(bean, bean4Autowired);
                    } else {
                        if (annotation.required()) {
                            throw new Exception("未找到注入类...");
                        }
                    }
                }
            }
        }
    }

    private Object getAutowiredBeanByField(Field field) {
        Object bean = singletonMap.get(field.getName());
        Class<?> fieldType = field.getType();
        if (bean != null) {
            boolean isFieldType = false;
            if (fieldType.isInterface()) {
                for (ScanClass scanClass : scanClassList) {
                    Class<?> beanClass = scanClass.getBeanClass();
                    Class<?> interfaceClass;
                    if (isFieldType = bean.getClass() == beanClass && (interfaceClass = scanClass.getInterfaceClass()) != null && interfaceClass.getName().equals(fieldType.getName())) {
                        break;
                    }
                }
            } else {
                isFieldType = bean.getClass().getName().equals(fieldType.getName());
            }
            if (!isFieldType) {
                bean = null;
            }
        }
        if (bean == null) {
            String beanName = null;
            if (fieldType.isInterface()) {
                for (ScanClass scanClass : scanClassList) {
                    if (scanClass.getInterfaceFlag() && scanClass.getInterfaceClass().getName().equals(fieldType.getName())) {
                        beanName = scanClass.getBeanClass().getSimpleName();
                    }
                }
            } else {
                beanName = fieldType.getSimpleName();
            }
            if (beanName != null && beanName.length() > 0) {
                bean = singletonMap.get((beanName.substring(0, 1).toLowerCase() + beanName.substring(1)));
            }
        }
        return bean;
    }

    private void initializeBeans() {
        for (Map.Entry<String, Object> singletonEntry : singletonMap.entrySet()) {
            String key = singletonEntry.getKey();
            Object bean = singletonEntry.getValue();
            ProxyFactory proxyFactory;
            if (enableTransactional(bean) && (proxyFactory = (ProxyFactory) singletonMap.get("proxyFactory")) != null) {
                boolean isImpl = false;
                for (ScanClass scanClass : scanClassList) {
                    Class<?> beanClass = scanClass.getBeanClass();
                    if (bean.getClass() == beanClass) {
                        isImpl = scanClass.getInterfaceClass() != null;
                    }
                }
                singletonMap.put(key, isImpl ? proxyFactory.getJdkProxy(bean) : proxyFactory.getCglibProxy(bean));
            }
        }
    }

    private boolean enableTransactional(Object bean) {
        Class<?> beanClass = bean.getClass();
        Transactional annotation = beanClass.getAnnotation(Transactional.class);
        if (annotation == null) {
            Method[] methods = beanClass.getDeclaredMethods();
            for (Method method : methods) {
                if ((annotation = method.getAnnotation(Transactional.class)) != null) {
                    break;
                }
            }
        }
        return annotation != null;
    }

    public Map<String, Object> initSingletonMap(String contextConfig) {
        try {
            intContextConfig(contextConfig);
            setBeansToSingletonMap();
            populateBeans();
            initializeBeans();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return singletonMap;
    }
}